// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2025-01-08

package container

import (
	"gitee.com/go-libs/perm/types"
	"sort"
	"sync"
)

type (
	// ApplicationContainer is a component interface that store resources,
	// roles and permissions. It is used to verify whether one of given roles
	// is authorized to the given resource.
	ApplicationContainer interface {
		Extract()

		GetApplication() types.Application

		// GetPermissionsList read permissions list with given roles of unique.
		GetPermissionsList(roles ...types.Role) []string

		GetResourceContainer(typ types.Resource) (rc ResourceContainer, has bool)
		GetResourceContainers() (rc []ResourceContainer)
		GetRoleContainer(typ types.Role) (rc RoleContainer, has bool)
		GetRoleContainers() (rc []RoleContainer)
		SetResourceContainer(rc ResourceContainer) (err error)
		SetRoleContainer(rc RoleContainer) (err error)

		// Verify whether the roles in the list are authorized to resource. If
		// any role is authorized to verify successfully.
		Verify(resource types.Resource, roles ...types.Role) types.Verification
	}

	application struct {
		Application   types.Application
		Permissions   map[string][]string
		Resources     map[types.Resource]ResourceContainer
		ResourcesKeys []string
		Roles         map[types.Role]RoleContainer
		RolesKeys     []string

		mu *sync.RWMutex
	}
)

func NewApplicationContainer(typ types.Application) ApplicationContainer {
	return &application{
		Application:   typ,
		Permissions:   make(map[string][]string),
		Resources:     make(map[types.Resource]ResourceContainer),
		ResourcesKeys: make([]string, 0),
		Roles:         make(map[types.Role]RoleContainer),
		RolesKeys:     make([]string, 0),

		mu: &sync.RWMutex{},
	}
}

func (o *application) Extract() {
	var ps = make(map[types.Permission][]types.Resource)

	// Build mapping.
	// {
	//    "user:add": ["/user/add", "/user/add/sub"],
	// }
	for _, r := range o.Resources {
		if _, ok := ps[r.GetPermission()]; !ok {
			ps[r.GetPermission()] = make([]types.Resource, 0)
		}
		ps[r.GetPermission()] = append(ps[r.GetPermission()], r.GetResource())
	}

	// Relations.
	for _, r := range o.Roles {
		for p := range r.GetPermissions() {
			for _, k := range ps[p] {
				if kc, ok := o.GetResourceContainer(k); ok {
					kc.Authorize(r.GetRole())
				}
			}
		}
	}
}

func (o *application) GetApplication() types.Application { return o.Application }

func (o *application) GetResourceContainer(typ types.Resource) (rc ResourceContainer, has bool) {
	rc, has = o.Resources[typ]
	return
}

func (o *application) GetResourceContainers() (rc []ResourceContainer) {
	rc = make([]ResourceContainer, 0)
	for _, k := range o.ResourcesKeys {
		if v, ok := o.Resources[types.Resource(k)]; ok {
			rc = append(rc, v)
		}
	}
	return
}

func (o *application) GetPermissionsList(roles ...types.Role) []string {
	var (
		list   = make([]string, 0)
		mapper = make(map[string]string)
	)
	for _, r := range roles {
		if v, ok := o.Roles[r]; ok {
			for _, k := range v.GetPermissionsValues() {
				if _, has := mapper[k]; !has {
					list = append(list, k)
					mapper[k] = k
				}
			}
		}
	}
	return list
}

func (o *application) GetRoleContainer(typ types.Role) (rc RoleContainer, has bool) {
	rc, has = o.Roles[typ]
	return
}

func (o *application) GetRoleContainers() (rc []RoleContainer) {
	rc = make([]RoleContainer, 0)
	for _, k := range o.RolesKeys {
		if v, ok := o.Roles[types.Role(k)]; ok {
			rc = append(rc, v)
		}
	}
	return
}

func (o *application) SetResourceContainer(rc ResourceContainer) (err error) {
	if _, ok := o.Resources[rc.GetResource()]; ok {
		err = types.ErrResourceRepeated
		return
	}

	o.Resources[rc.GetResource()] = rc
	o.ResourcesKeys = append(o.ResourcesKeys, rc.GetResource().String())

	if len(o.ResourcesKeys) > 0 {
		sort.Strings(o.ResourcesKeys)
	}
	return
}

func (o *application) SetRoleContainer(rc RoleContainer) (err error) {
	if _, ok := o.Roles[rc.GetRole()]; ok {
		err = types.ErrRoleRepeated
		return
	}

	o.Roles[rc.GetRole()] = rc
	o.RolesKeys = append(o.RolesKeys, rc.GetRole().String())

	if len(o.RolesKeys) > 0 {
		sort.Strings(o.RolesKeys)
	}
	return
}

func (o *application) Verify(resource types.Resource, roles ...types.Role) types.Verification {
	// Run resource verifier.
	if rc, ok := o.Resources[resource]; ok {
		return rc.Verify(roles...)
	}

	// The resource does not exist.
	return types.VerificationResourceNotFound
}
